import ctypes
import time
from functools import wraps
from inspect import signature
from threading import Thread, Lock, RLock
from typing import get_type_hints

__all__ = ['autorun', 'fixed', 'overload', 'Singleton', 'time_limit', 'synchronized', 'lazy', 'tag', 'Class']


def autorun(*args, **kwargs):
    """装饰后自动执行，args和kwargs是执行时的参数，执行完毕后函数名会变成执行的结果
    Example:
    >>> @autorun(1,2)
    ... def f(x,y):
    ...     print(x,y)
    ...
    1 2
    """

    def decorator(func):
        return func(*args, **kwargs)

    return decorator


def fixed(*vals, **kw_vals):
    """用于闭包中绑定变量
    与fixed的参数同位置的参数会被视为绑定的变量，其余参数需要在调用时传入
    Example:
    >>> x=233
    >>> @fixed(x)
    ... def f(x):
    ...     print(x)
    ...
    >>> def g():
    ... 	print(x)
    ...
    >>> x=666
    >>> f()
    233
    >>> g()
    666
    """

    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            return func(*vals, *args, **kw_vals, **kwargs)

        return wrapper

    return decorator


def overload(func, *, funcs={}):
    """重载函数，不同的参数个数或者类型注解都可以用于重载
    （请无视funcs，这是用来存储重载的函数的）
    Example:
    >>> @overload
    ... def f(x:int):
    ...     print('int ',x)
    ...
    >>> @overload
    ... def f(x:str):
    ...     print('str ',x)
    ...
    >>> @overload
    ... def f(x,y):
    ...     print('two ',x,y)
    ...
    >>> f(233)
    int  233
    >>> f('abc')
    str  abc
    >>> f(1,2)
    two  1 2
    """
    name = func.__qualname__
    funcs.setdefault(name, []).append(func)

    @wraps(func)
    def wrapper(*args, **kwargs):
        error_list = []
        for f in funcs[name]:
            sig = signature(f)
            annotations = get_type_hints(f)
            try:
                bound = sig.bind(*args, **kwargs)
                for k, v in bound.arguments.items():
                    if k in annotations and not isinstance(v, annotations[k]):
                        raise TypeError(f'参数类型错误：参数{k}应为{annotations[k]!r}，实为{type(v)!r}')
            except TypeError as e:
                error_list.append(f'匹配参数{sig}：{e}')
            else:
                return f(*args, **kwargs)
        raise TypeError('没有符合参数的重载\n' + '\n'.join(error_list))

    wrapper.all = funcs[name]
    wrapper.__doc__ = '\n\n'.join(f'{name}{signature(f)}\n{f.__doc__}' for f in funcs[name])
    return wrapper


class _SingletonMeta(type):
    def __init__(cls, name, bases, attrs):
        super().__init__(name, bases, attrs)
        cls.__instance = None
        cls.__instance_lock = Lock()

    def __call__(cls, *args, **kwargs):
        if cls.__instance is None:
            with cls.__instance_lock:
                if cls.__instance is None:
                    cls.__instance = super().__call__(*args, **kwargs)
        return cls.__instance


class Singleton(metaclass=_SingletonMeta):
    """将子类变为单例类
    Example:
    >>> class C(Singleton):
    ... 	pass
    >>> C() is C()
    True
    >>> class D(C):
    ...     pass
    >>> D() is D()
    True
    >>> C() is D()
    False
    """
    pass


def time_limit(timeout, default=None):
    """限时执行一个函数，超时后如果函数没有返回结果，则会强制结束该函数并返回default
    Example:
    >>> @time_limit(0.3)
    ... def f(x):
    ... 	time.sleep(x)
    ... 	print(x)
    ... 	return x
    >>> def g():
    ... 	print(f(0.1))
    ... 	print(f(0.5))
    ... 	time.sleep(0.5)
    >>> g()
    0.1
    0.1
    None
    """

    def decorator(fun):
        @wraps(fun)
        def wrapper(*args, **kwargs):
            result = default

            def target():
                nonlocal result
                result = fun(*args, **kwargs)

            thread = Thread(target=target)
            thread.start()
            thread.join(timeout)
            if thread.is_alive():
                ctypes.pythonapi.PyThreadState_SetAsyncExc(
                    ctypes.c_long(thread.ident),
                    ctypes.py_object(SystemExit)
                )
            return result

        return wrapper

    return decorator


def synchronized(fun):
    """将函数fun添加调用锁，使其同一时刻只能执行一个
    Example:
    >>> @synchronized
    ... def f(x):
    ...     for i in range(3):
    ...         print(x,i)
    >>> def g():
    ...     t1=Thread(target=f,args=(1,))
    ...     t2=Thread(target=f,args=(2,))
    ...     t1.start()
    ...     t2.start()
    ...     t1.join()
    ...     t2.join()
    >>> g()
    1 0
    1 1
    1 2
    2 0
    2 1
    2 2
    """
    lock = RLock()

    @wraps(fun)
    def wrapper(*args, **kwargs):
        with lock:
            return fun(*args, **kwargs)

    return wrapper


def lazy(target, *args, **kwargs):
    """惰性求值，在使用成员时才会实例化对象
    Example:
    >>> class C:
    ...     def __init__(self,x):
    ...         self.x=x
    ...         print('init')
    >>> c=lazy(C,2)
    >>> print(c.x)
    init
    2
    """
    obj = None

    class Proxy:
        def __getattribute__(self, key):
            nonlocal obj
            if obj is None:
                obj = target(*args, **kwargs)
            return obj.__getattribute__(key)

    return Proxy()


def tag(fun):
    """实现跨函数返回。被装饰的函数会获得一个ret方法，调用ret就可以返回到该函数
    Example:
    >>> @tag
    ... def f():
    ...     def g():
    ...         f.ret(233)
    ...     g()
    >>> f()
    233
    """
    t = Exception()

    @wraps(fun)
    def wrapper(*args, **kwargs):
        try:
            return fun(*args, **kwargs)
        except Exception as e:
            if e is t:
                return e.res
            raise

    def ret(res):
        t.res = res
        raise t

    wrapper.ret = ret
    return wrapper


class Class(type):
    """实现类似scala的类定义方法
    Example:
    >>> @Class
    ... def C(self, name):
    ...     self.age=233
    ...     @C.add_method
    ...     def f():
    ...         print(name,self.age)
    ...     if name=='bob':
    ...         return f
    >>> C('alice').f()
    alice 233
    >>> C('bob')()
    bob 233
    """

    def __new__(mcs, cls_fun):
        return super().__new__(mcs, cls_fun.__name__, (), {'cls_fun': cls_fun})

    def __call__(cls, *args, **kwargs):
        obj = super().__call__()
        ret = cls.cls_fun(obj, *args, **kwargs)
        return obj if ret is None else ret

    def add_method(cls, fun):
        setattr(cls, fun.__name__, staticmethod(fun))
        return fun


if __name__ == '__main__':
    import doctest

    doctest.testmod()
